package com.github.phoenics.teacher.itheima.netty.first;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

/**
 * 第一个Netty程序 server
 *
 * @author Phoenics Chow
 * on 2020年01月13日
 */
public class TimeServer {
    public static void main(String[] args) {
        int port = 9898;
        new TimeServer().bind(port);
    }

    public void bind(int port) {
        /**
         * interface EventLoopGroup extends EventExecutorGroup extends ScheduledExecutorService extends ExecutorService
         * 配置服务端的 NIO 线程池,用于网络事件处理，实质上他们就是 Reactor 线程组
         * bossGroup 用于服务端接受客户端连接，Reactor的核心在于一个线程可以处理多个请求
         * workerGroup 用于进行 SocketChannel 网络读写,采用轮询，有数据到就开线程处理，没有数据就处理下一个；
         *
         *
         * */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /** ServerBootstrap 是 Netty 用于启动 NIO 服务端的辅助启动类，用于降低开发难度
             * */
            ServerBootstrap b = new ServerBootstrap();

            /**
            Reactor使用单线程，然后Work使用多线程，即是多线程模型。
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            EventLoop bossLoop = eventLoopGroup.next();

             只是Main Reactor是单线程进行事件循环。虽然也可以构造多线程，但是没有什么实际意义。
             因为netty中在绑定端口时只会使用Group中的一个EventLoop绑定到Selector上，即是使用了EventLoopGroup。

             当然对于同个应用如果监听多个端口，使用多个ServerBootStrap共享一个boss，
             那样Main Reactor也是多线程模式，才有意义。
            */
            //b.group(bossLoop, workerGroup)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    /**
                     *  ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数，
                     *  函数listen(int socketfd,int backlog)用来初始化服务端可连接队列，
                     *  服务端处理客户端连接请求是顺序处理的，所以同一时间只能处理一个客户端连接，
                     *  多个客户端来的时候，服务端将不能处理的客户端连接请求放在队列中等待处理，
                     *  backlog参数指定了队列的大小
                     *
                     *  socket的标准参数，并不是netty自己的
                     *
                     */
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());

            /**服务器启动辅助类配置完成后，调用 bind 方法绑定监听端口，调用 sync 方法同步等待绑定操作完成*/
            ChannelFuture f = b.bind(port).sync();

            System.out.println(Thread.currentThread().getName() + ",服务器开始监听端口，等待客户端连接.........");
            /**下面会进行阻塞，等待服务器连接关闭之后 main 方法退出，程序结束
             *
             * */
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            /**优雅退出，释放线程池资源*/
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    /**
    ChannelInitializer：它是一个特殊的ChannelInboundHandler,
    当channel注册到eventLoop上面时，对channel进行初始化
     ChannelHandler：用来处理业务逻辑的代码，ChannelHandler是一个父接口，
     ChannelnboundHandler和ChannelOutboundHandler都继承了该接口，它们分别用来处理入站和出站。
     */
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            //arg0.pipeline().addFirst()
            //arg0.pipeline().addAfter()
            //arg0.pipeline().addBefore()
            arg0.pipeline().addLast(new TimeServerHandler());

        }
    }


}
